/**
 * @file		ipcu.c
 * @brief		IPCU (Inter-Processor Communications Unit)
 * @note		None
 * @attention	None
 * 
 * <B><I>ALL RIGHTS RESERVED, COPYRIGHT&copy; SOCIONEXT INCORPORATED 2016</I></B>
 */

/* CMSIS include */
#include "ARMCM0.h"

/* FreeRTOS includes. */
#include "FreeRTOS.h"
#include "event_groups.h"
#include "task.h"

/* driver includes */
#include "dd_ipcu.h"

#include "ipcu.h"
#include "TemperatureSensor.h"

#include "user_custom.h"

// Define
#define		IPCU_MAILBOX_CM0_LINUX				(4)		// MailBox:12(Unit1-4) CM0   -> Linux
#define		IPCU_MAILBOX_LINUX_CM0				(5)		// MailBox:13(Unit1-5) Linux -> CM0
#define		IPCU_MAILBOX_RTOS_CM0				(6)		// MailBox:14(Unit1-6) RTOS  -> CM0
#define		IPCU_MAILBOX_CM0_RTOS				(7)		// MailBox:15(Unit1-7) CM0   -> RTOS

#define		IPCU_INTERRUPT_CH_CM0_LINUX_SRC		(12)	// Interrupt Ch:12 CM0   -> Linux source
#define		IPCU_INTERRUPT_CH_CM0_LINUX_DST		(5)		// Interrupt Ch:5  CM0   -> Linux destination
#define		IPCU_INTERRUPT_CH_LINUX_CM0_SRC		(10)	// Interrupt Ch:10 Linux -> CM0   source
#define		IPCU_INTERRUPT_CH_LINUX_CM0_DST		(13)	// Interrupt Ch:13 Linux -> CM0   destination
#define		IPCU_INTERRUPT_CH_CM0_RTOS_SRC		(15)	// Interrupt Ch:15 CM0   -> RTOS  source
#define		IPCU_INTERRUPT_CH_CM0_RTOS_DST		(4)		// Interrupt Ch:4  CM0   -> RTOS  destination

#define		IPCU_DATA_BUFF_NUM					(100)
#define		IPCU_RESPONSE_COMMAND_MAX			(10)
#define		IPCU_REQUEST_COMMAND_MAX			(9)
#define		IPCU_DATA_MAX						(5)
#define		IPCU_NOTIFY_TYPE_NUM				(8)

// Global
unsigned long	ulIPCUReceiveCommand[IPCU_REQUEST_COMMAND_MAX];

#ifdef CO_MEMORY_1GB
unsigned long	ulIPCUResponseCommand[IPCU_DATA_BUFF_NUM][IPCU_RESPONSE_COMMAND_MAX] __attribute__((section(".IPCU_RESPONSE_COMMAND_BUFFER_1GB")));
#else	// CO_MEMORY_1GB
unsigned long	ulIPCUResponseCommand[IPCU_DATA_BUFF_NUM][IPCU_RESPONSE_COMMAND_MAX] __attribute__((section(".IPCU_RESPONSE_COMMAND_BUFFER")));
#endif	// CO_MEMORY_1GB
unsigned long	ulIPCUResponseData[IPCU_DATA_BUFF_NUM][IPCU_DATA_MAX];
unsigned long	ulIPCUResponseDataWriteIndex = 0;
unsigned long	ulIPCUResponseDataReadIndex = 0;

#ifdef CO_MEMORY_1GB
unsigned long	ulIPCUNotifyCommand[IPCU_DATA_BUFF_NUM][D_IPCU_NOTIFY_COMMAND_MAX] __attribute__((section(".IPCU_NOTIFY_COMMAND_BUFFER_1GB")));
#else	// CO_MEMORY_1GB
unsigned long	ulIPCUNotifyCommand[IPCU_DATA_BUFF_NUM][D_IPCU_NOTIFY_COMMAND_MAX] __attribute__((section(".IPCU_NOTIFY_COMMAND_BUFFER")));
#endif	// CO_MEMORY_1GB
unsigned long	ulIPCUNotifyData[IPCU_DATA_BUFF_NUM][IPCU_DATA_MAX];
unsigned long	ulIPCUNotifyDataWriteIndex = 0;
unsigned long	ulIPCUNotifyDataReadIndex = 0;

int				iNotifyDestination[IPCU_NOTIFY_TYPE_NUM] = { 0 };

// Local function.
static void prvIPCUAckCallback(unsigned int uiIntCh);
static void prvIPCURequestCallback(unsigned int uiIntCh);

/**
IPCU acknowledge callback function.
*/
static void prvIPCUAckCallback(unsigned int uiIntCh)
{
	unsigned long	ulMailboxAddress;
	unsigned long	ulMailboxNum;
	unsigned long	ulAckStatus;
	
	// Get Mail Box Number.
	Dd_IPCU_Get_MB_Adr(uiIntCh, &ulMailboxAddress);
	ulMailboxNum = (ulMailboxAddress - 0x100) >> 7;
	
	// Read acknowlegde status.
	Dd_IPCU_Get_MB_Ack_Stat(ulMailboxNum, &ulAckStatus);
	
	// Clear acknowlegde.
	Dd_IPCU_Clr_MB_Ack(ulMailboxNum, ulAckStatus);
	
	// Release Mail Box.
	Dd_IPCU_Set_MB_Src(ulMailboxNum, 0);
	
	return;
}

/**
IPCU request callback function.
*/
static void prvIPCURequestCallback(unsigned int uiIntCh)
{
	unsigned long	ulMailboxAddress;
	unsigned long	ulMailboxNum;
	unsigned long	ulMode;
	unsigned long	ulRequestAddress;
	unsigned long	ulRequestSize;
	unsigned long	ulResponseSize = 12;
	int				iRet;
	int				iLoop;
	BaseType_t		xHigherPriorityTaskWoken, xResult;

	// Get Mailbox Number.
	Dd_IPCU_Get_MB_Adr(uiIntCh, &ulMailboxAddress);
	ulMailboxNum = (ulMailboxAddress - 0x100) >> 7;
	
	// Get Data : Address, Size
	Dd_IPCU_Get_MB_Data(ulMailboxNum, 1, &(ulRequestAddress));
	Dd_IPCU_Get_MB_Data(ulMailboxNum, 2, &(ulRequestSize));
	ulRequestAddress += 0x20000000;
	
	// Get Data.
	for( iLoop = 0; iLoop < (ulRequestSize / 4); iLoop++ ) {
		ulIPCUReceiveCommand[iLoop] = *((unsigned long*)(ulRequestAddress + (iLoop * 4)));
	}
	
	// analyze receive data.
	iRet = iInterpretCommand((unsigned*)&(ulIPCUReceiveCommand[0]));
	
	if (ulIPCUReceiveCommand[0] != 0x01) {
		// Command:WRITE/READ/RMW/SET/GET/START/STOP/StateAterStandby - send response.
		
		// Clear Response command
		for (iLoop = 0; iLoop < IPCU_RESPONSE_COMMAND_MAX; iLoop++ ) {
			ulIPCUResponseCommand[ulIPCUResponseDataWriteIndex][iLoop] = 0;
		}
		
		// Set Response command & Response data
		ulIPCUResponseCommand[ulIPCUResponseDataWriteIndex][0] = ulIPCUReceiveCommand[0] | 0x01000000;		// Command#0 : Command code
		ulIPCUResponseCommand[ulIPCUResponseDataWriteIndex][2] = iRet;										// Command#2 : result
		
		switch (ulIPCUReceiveCommand[0]) {
			case 0x03:	// READ
				ulIPCUResponseCommand[ulIPCUResponseDataWriteIndex][3] = ulIPCUReceiveCommand[8];			// Command#3 : read data
				ulResponseSize = 16;
				break;
			case 0x06:	// GET
				ulIPCUResponseCommand[ulIPCUResponseDataWriteIndex][1] = ulIPCUReceiveCommand[1];			// Command#1 : Sub-Command code
				switch (ulIPCUReceiveCommand[1]) {
					case 0x0A:		// Temperature Sensor
						ulIPCUResponseCommand[ulIPCUResponseDataWriteIndex][3] = ulIPCUReceiveCommand[2];	// Command#3 : Temperature data
						ulResponseSize = 16;
						break;
					case 0x0B: //E_FJ_IPCU_GET_ADVFS_EN
						ulIPCUResponseCommand[ulIPCUResponseDataWriteIndex][3] = ulIPCUReceiveCommand[2]; //Command#3 ADvfs-en
						ulResponseSize = 16;
						break;
					case 0x0B1: //E_FJ_IPCU_GET_ADVFS
						ulIPCUResponseCommand[ulIPCUResponseDataWriteIndex][3] = ulIPCUReceiveCommand[2]; //Command#3 AVS
						ulIPCUResponseCommand[ulIPCUResponseDataWriteIndex][4] = ulIPCUReceiveCommand[3]; //Command#4 DVFS
						ulResponseSize = 20;
						break;
					case 0x0B2: //E_FJ_IPCU_GET_PLL1_FRQ_BUF
						ulIPCUResponseCommand[ulIPCUResponseDataWriteIndex][3] = ulIPCUReceiveCommand[2]; //Command#3 H-Frq
						ulIPCUResponseCommand[ulIPCUResponseDataWriteIndex][4] = ulIPCUReceiveCommand[3]; //Command#4 L-Frq
						ulResponseSize = 20;
						break;
					case 0x0B3: //E_FJ_IPCU_GET_TEMPERA_BUF
						for (iLoop = 3; iLoop < 7; iLoop++ ) {
							ulIPCUResponseCommand[ulIPCUResponseDataWriteIndex][iLoop] = ulIPCUReceiveCommand[iLoop - 1];
						}
						ulResponseSize = 28; //7 * 4
						break;
					case 0x3001:	// Calendar data
						for (iLoop = 3; iLoop < IPCU_RESPONSE_COMMAND_MAX; iLoop++ ) {
							ulIPCUResponseCommand[ulIPCUResponseDataWriteIndex][iLoop] = ulIPCUReceiveCommand[iLoop - 1];
						}
						ulResponseSize = 40;
						break;
					case 0xAB01:
						ulIPCUResponseCommand[ulIPCUResponseDataWriteIndex][3] = ulIPCUReceiveCommand[2];
						ulResponseSize = 16;
						break;
					default:
						ulResponseSize = 12;
						break;
				}
				break;
			case 0x07:	// START
				ulIPCUResponseCommand[ulIPCUResponseDataWriteIndex][1] = ulIPCUReceiveCommand[1];			// Command#1 : Sub-Command code
				ulResponseSize = 12;
				// Notify Destination
				if( (iRet == 0) && (ulIPCUReceiveCommand[1] >= 0x03) && (ulIPCUReceiveCommand[1] <= 0x0A) ) {
					if( ulMailboxNum == IPCU_MAILBOX_LINUX_CM0 ) {
						// Linux
						iNotifyDestination[ulIPCUReceiveCommand[1] - 3] = 1;
					}
					else {
						// RTOS
						iNotifyDestination[ulIPCUReceiveCommand[1] - 3] = 0;
					}
				}
				break;
			case 0x05:	// SET
			case 0x08:	// STOP
				ulIPCUResponseCommand[ulIPCUResponseDataWriteIndex][1] = ulIPCUReceiveCommand[1];			// Command#1 : Sub-Command code
				ulResponseSize = 12;
				break;
			// ### DELETE_SECURE
			case 0x09:	// CRYPTO
				ulIPCUResponseCommand[ulIPCUResponseDataWriteIndex][3] = ulIPCUReceiveCommand[8];			// Command#3 : result
				ulResponseSize = 16;
				break;
			// ### DELETE_SECURE
			default:
				ulResponseSize = 12;
				break;
		}
		
		// Set Response data (DATA Register setting value)
		// Data#0 : Mailbox Number
		if (ulMailboxNum == IPCU_MAILBOX_LINUX_CM0) {
			// Request Mailbox=13(Linux-CM0) -> Response Mailbox=12(CM0-Linux)
			ulIPCUResponseData[ulIPCUResponseDataWriteIndex][0] = IPCU_MAILBOX_CM0_LINUX + 8;
		}
		else {
			// Request Mailbox=14(RTOS-CM0) -> Response Mailbox=15(CM0-RTOS)
			ulIPCUResponseData[ulIPCUResponseDataWriteIndex][0] = IPCU_MAILBOX_CM0_RTOS + 8;
		}
		// Data#1 : Address
		ulIPCUResponseData[ulIPCUResponseDataWriteIndex][1] = (unsigned long)&(ulIPCUResponseCommand[ulIPCUResponseDataWriteIndex][0]) - 0x20000000;
		// Data#2 : Size
		ulIPCUResponseData[ulIPCUResponseDataWriteIndex][2] = ulResponseSize;
		// Data#3 : Continue (fixed 0)
		ulIPCUResponseData[ulIPCUResponseDataWriteIndex][3] = 0;
		// Data#4 : Total size
		ulIPCUResponseData[ulIPCUResponseDataWriteIndex][4] = ulResponseSize;
		
		ulIPCUResponseDataWriteIndex++;
		if (ulIPCUResponseDataWriteIndex >= IPCU_DATA_BUFF_NUM) {
			ulIPCUResponseDataWriteIndex = 0;
		}
		
		xHigherPriorityTaskWoken = pdFALSE;
		
		// Set Event Flag : Send response.
		xResult = xEventGroupSetBitsFromISR(xNotifyEventFlag, (1 << NFY_SEND_RESPONSE), &xHigherPriorityTaskWoken);
		if (xResult == pdPASS) {
			portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
		}
	}
	else {
		// Command:GO - no response.
	}
	
	// Clear destination.
	Dd_IPCU_Clr_MB_Dst_Unit(ulMailboxNum, uiIntCh);
	
	// Get Mode.
	Dd_IPCU_Get_MB_Mode(ulMailboxNum, &ulMode);
	if ((ulMode == E_DD_IPCU_MB_MODE_MANUAL_1) || (ulMode == E_DD_IPCU_MB_MODE_MANUAL_2)) {
		// Manual Mode ---> Set Ack.
		Dd_IPCU_Set_MB_Ack_Unit(ulMailboxNum, uiIntCh);
	}
}

/**
IPCU Initialization.
*/
void vIPCUInit(void)
{
	// Set Callback function.
	Dd_IPCU_Set_Req_CB(IPCU_MAILBOX_LINUX_CM0, (VP_CALLBACK)prvIPCURequestCallback);
	Dd_IPCU_Set_Ack_CB(IPCU_MAILBOX_CM0_LINUX, (VP_CALLBACK)prvIPCUAckCallback);
	Dd_IPCU_Set_Req_CB(IPCU_MAILBOX_RTOS_CM0, (VP_CALLBACK)prvIPCURequestCallback);
	Dd_IPCU_Set_Ack_CB(IPCU_MAILBOX_CM0_RTOS, (VP_CALLBACK)prvIPCUAckCallback);
}

/**
IPCU Send Response message.
*/
void vIPCUSendResponse(void)
{
	int				iLoop;
	unsigned long	ulMailboxNum = 0;
	unsigned long	ulSrcCh = 0;
	unsigned long	ulDstCh = 0;
	unsigned long	ulMailboxStatus = 0;

	ulMailboxNum = ulIPCUResponseData[ulIPCUResponseDataReadIndex][0] - 8;
	
	if(ulMailboxNum == IPCU_MAILBOX_CM0_LINUX) {
		ulSrcCh = IPCU_INTERRUPT_CH_CM0_LINUX_SRC;
		ulDstCh = IPCU_INTERRUPT_CH_CM0_LINUX_DST;
	}
	else {
		ulSrcCh = IPCU_INTERRUPT_CH_CM0_RTOS_SRC;
		ulDstCh = IPCU_INTERRUPT_CH_CM0_RTOS_DST;
	}
	
	// Get Mailbox Status.
	Dd_IPCU_Get_MB_Stat_MB(ulMailboxNum, &ulMailboxStatus);
	if (ulMailboxStatus == 1) {
		// Mailbox is in use.
		return;
	}
	
	// Set Mailbox.
	Dd_IPCU_Set_MB_Src(ulMailboxNum, (1 << ulSrcCh));
	
	// Set destination.
	Dd_IPCU_Set_MB_Dst_Unit(ulMailboxNum, ulDstCh);
	
	// Set mode.
	Dd_IPCU_Set_MB_Mode(ulMailboxNum, E_DD_IPCU_MB_MODE_MANUAL_2);
	
	// Set Data.
	//  Data#0~#4 : Response data
	for( iLoop = 0; iLoop < IPCU_DATA_MAX; iLoop++ ) {
		Dd_IPCU_Set_MB_Data(ulMailboxNum, iLoop, ulIPCUResponseData[ulIPCUResponseDataReadIndex][iLoop]);
	}
	//  Data#5~#8 : 0 (No data)
	for( iLoop = IPCU_DATA_MAX; iLoop < 9; iLoop++ ) {
		Dd_IPCU_Set_MB_Data(ulMailboxNum, iLoop, 0);
	}
	
	ulIPCUResponseDataReadIndex++;
	if (ulIPCUResponseDataReadIndex >= IPCU_DATA_BUFF_NUM) {
		ulIPCUResponseDataReadIndex = 0;
	}
	
	// Start sending.
	Dd_IPCU_Set_MB_Send(ulMailboxNum, 1);
}

/**
IPCU Set Notify Command Data.
*/
void vIPCUSetNotifyCommand(unsigned long ulCommandData[D_IPCU_NOTIFY_COMMAND_MAX], unsigned long ulSize)
{
	int	iLoop;
	int	iNotifyType = 0;
	
	// Set Notify command
	for( iLoop = 0; iLoop < ulSize; iLoop++ ) {
		ulIPCUNotifyCommand[ulIPCUNotifyDataWriteIndex][iLoop] = ulCommandData[iLoop];
	}
	if( ulSize < D_IPCU_NOTIFY_COMMAND_MAX ) {
		// Clear Notify command buffer
		for (iLoop = ulSize; iLoop < D_IPCU_NOTIFY_COMMAND_MAX; iLoop++) {
			ulIPCUNotifyCommand[ulIPCUNotifyDataWriteIndex][iLoop] = 0;
		}
	}
	
	iNotifyType = (ulCommandData[0] & 0x0000000F) - 3;
	if((iNotifyType < 0) || ( iNotifyType >= IPCU_NOTIFY_TYPE_NUM )) {
		// paramter error
		return;
	}
	
	// Set Notify data
	// Data#0 : Mailbox Number
	if( iNotifyDestination[iNotifyType] == 1 ) {
		// Mailbox=12(CM0-Linux)
		ulIPCUNotifyData[ulIPCUNotifyDataWriteIndex][0] = IPCU_MAILBOX_CM0_LINUX + 8;
	}
	else {
		// Mailbox=15(CM0-RTOS)
		ulIPCUNotifyData[ulIPCUNotifyDataWriteIndex][0] = IPCU_MAILBOX_CM0_RTOS + 8;
	}
	// Data#1 : Address
	ulIPCUNotifyData[ulIPCUNotifyDataWriteIndex][1] = (unsigned long)&(ulIPCUNotifyCommand[ulIPCUNotifyDataWriteIndex][0]) - 0x20000000;
	// Data#2 : Size
	ulIPCUNotifyData[ulIPCUNotifyDataWriteIndex][2] = ulSize * 4;
	// Data#3 : Continue (fixed 0)
	ulIPCUNotifyData[ulIPCUNotifyDataWriteIndex][3] = 0;
	// Data#4 : Total size
	ulIPCUNotifyData[ulIPCUNotifyDataWriteIndex][4] = ulSize * 4;
	
	ulIPCUNotifyDataWriteIndex++;
	if( ulIPCUNotifyDataWriteIndex >= IPCU_DATA_BUFF_NUM ) {
		ulIPCUNotifyDataWriteIndex = 0;
	}
}

/**
IPCU Send Notification message.
*/
void vIPCUSendNotification(unsigned long ulNotifyCode)
{
	int				iLoop;
	unsigned long	ulMailboxStatus = 0;
	unsigned long	ulMailboxNum;
	unsigned long	ulSrcCh;
	unsigned long	ulDstCh;

	// Notify Code check
	if( (ulNotifyCode < 0x03) || (ulNotifyCode > 0x0A) ) {
		// out of range
		return;
	}
	
	ulMailboxNum = ulIPCUNotifyData[ulIPCUNotifyDataReadIndex][0] - 8;
	
	if( ulMailboxNum == IPCU_MAILBOX_CM0_LINUX ) {
		ulSrcCh = IPCU_INTERRUPT_CH_CM0_LINUX_SRC;
		ulDstCh = IPCU_INTERRUPT_CH_CM0_LINUX_DST;
	}
	else {
		ulSrcCh = IPCU_INTERRUPT_CH_CM0_RTOS_SRC;
		ulDstCh = IPCU_INTERRUPT_CH_CM0_RTOS_DST;
	}

	// Get Mailbox Status.
	Dd_IPCU_Get_MB_Stat_MB(ulMailboxNum, &ulMailboxStatus);
	if( ulMailboxStatus == 1 ) {
		// Mailbox is in use.
		return;
	}
	
	// Set Mailbox.
	Dd_IPCU_Set_MB_Src(ulMailboxNum, (1 << ulSrcCh));
	
	// Set destination.
	Dd_IPCU_Set_MB_Dst_Unit(ulMailboxNum, ulDstCh);
	
	// Set mode.
	Dd_IPCU_Set_MB_Mode(ulMailboxNum, E_DD_IPCU_MB_MODE_MANUAL_2);
	
	// Set Data.
	//  Data#0~#4 : Notify data
	for( iLoop = 0; iLoop < IPCU_DATA_MAX; iLoop++ ) {
		Dd_IPCU_Set_MB_Data(ulMailboxNum, iLoop, ulIPCUNotifyData[ulIPCUNotifyDataReadIndex][iLoop]);
	}
	//  Data#5~#8 : 0 (No data)
	for( iLoop = IPCU_DATA_MAX; iLoop < 9; iLoop++ ) {
		Dd_IPCU_Set_MB_Data(ulMailboxNum, iLoop, 0);
	}
	
	ulIPCUNotifyDataReadIndex++;
	if( ulIPCUNotifyDataReadIndex >= IPCU_DATA_BUFF_NUM ) {
		ulIPCUNotifyDataReadIndex = 0;
	}
	
	// Start sending.
	Dd_IPCU_Set_MB_Send(ulMailboxNum, 1);
}
